home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / PowerPlant / DMultiStringLocator / source / DMultiStringLocator.cp < prev    next >
Text File  |  1996-07-06  |  14KB  |  469 lines

  1. // ===========================================================================
  2. // DMultiStringLocator.cp
  3. //    ---------------------
  4. //    ©1996 Eric Gundrum, All rights reserved.
  5. //
  6. //    The contents of this file may be freely altered and freely distributed
  7. //    in any form, provided this copyright statement is retained unaltered.
  8. //    Add your own changes below.
  9. //    ---------------------
  10. //    
  11. //    Based on source code provided in Practical Algorithms for Programmers, 
  12. //    by Binstock and Rex, published in 1995 by Addison Wesley.
  13. //
  14. //    Simultaneously search text for multiple strings using the Aho/Corasick 
  15. //    algorithm.  
  16. //
  17.  
  18. // Mac Headers
  19. #include <Types.h>        // for NULL
  20.  
  21. // PowerPlant Headers
  22. #include <LListIterator.h>
  23. #include <UDebugging.h>
  24.  
  25. // Library Headers
  26. #include "DMultiStringLocator.h"
  27.  
  28.  
  29. // ===========================================================================
  30. // ----------- public member function implementations ----------
  31. // ===========================================================================
  32. #pragma mark --- public members ---
  33. // ---------------------------------------------------------------------------
  34. //    Constructor:
  35. //    Create state tables used by the search.
  36. //
  37. //    inStringList is a list of pointers to DSearchStrings.
  38. //    Ownership of the contents of inStringList is NOT transferred. The contents of 
  39. //    inStringList must exist as long as this instance of DMultiStringLocator exists.
  40.  
  41. DMultiStringLocator::DMultiStringLocator
  42.     ( LList &inStringList        // list of search strings
  43.     , int inAlphabetSize        // size of BranchTable
  44.     )
  45.     :    mAlphabetSize( inAlphabetSize )
  46. {
  47.     int                theChar; 
  48.     stateT            theState;
  49.     DSearchString    *stringP    = NULL;
  50.     
  51.     if ( NULL == &inStringList ) throw ( input_item_NULL() );    // validate input
  52.     
  53.     // compute maximum number of possible states by adding lengths of all strings
  54.     mMaxState = 1;
  55.     LListIterator    stringScan ( inStringList, iterate_FromStart );
  56.     while ( stringScan.Next ( &stringP ) )
  57.     {
  58.         mMaxState += stringP->Length();
  59.     }
  60.     
  61.     // alloc arrays
  62.     MatchArray    = new int[mMaxState];            // list of matching chars
  63.     GotoArray    = new GotoTable[mMaxState];        // list of next states or branch ptr
  64.                                                 //    GotoArray is indexed by MatchArray contents
  65.     RetryArray    = new stateT[mMaxState];        // list of retry states
  66.     OutArray    = new LList[mMaxState];            // list of pointers to recognizable words
  67.     
  68.     // initialize arrays
  69.     for ( theState = state_begin; theState < mMaxState; ++theState )
  70.     {
  71.         MatchArray[theState]    = EMPTY_SLOT;
  72.     }
  73.     
  74.     // initialize state_array[state_begin]
  75.     mHighState    = state_begin;
  76.     AddStateTrans ( 'a', state_begin, FAIL_STATE );
  77.     AddStateTrans ( 'b', state_begin, FAIL_STATE );    // force a BranchTable
  78.     
  79.     // add each string to the state engine
  80.     stringScan.ResetTo ( iterate_FromStart );
  81.     while ( true == stringScan.Next ( &stringP ) )
  82.     {
  83.         if ( NULL != stringP ) AddString ( *stringP );
  84.     }
  85.     
  86.     // setup return to zero transitions for state[state_begin]
  87.     for ( theChar = 0; theChar < mAlphabetSize; ++theChar )
  88.     {
  89.         if ( FAIL_STATE == GotoArray[state_begin].BranchTable[theChar] )
  90.         {
  91.             GotoArray[state_begin].BranchTable[theChar] = state_begin;
  92.         }
  93.     }
  94.     
  95.     // compute failure array
  96.     RetryArrayInit();
  97. }
  98.  
  99.  
  100. // ---------------------------------------------------------------------------
  101. //    Search a ram-based buffer.
  102. //
  103. //    Returns an int indicating the maximum number of characters that could be 
  104. //    part of a match at the end of the buffer. This indicates what number of 
  105. //    bytes should be copied to the next buffer if there is another buffer to 
  106. //    search. This does not account for found strings embedded in the partial 
  107. //    match string. Such embedded strings will be found twice. Note that this 
  108. //    problem occurs only when a complete search is split over multiple calls.
  109. //    
  110. //    (I'm not yet sure how best to correct this design flaw. Currently I ignore 
  111. //    it, but one could work around it by checking the buffer location of each 
  112. //    match to see if it is a duplicate.)
  113.  
  114. int
  115. DMultiStringLocator::SearchBuffer ( Uchar *inBufferP, int inSize )
  116. {
  117.             unsigned char    theChar;                    // comparison character
  118.             stateT             currentState, nextState;
  119.             int                matchChar, charLength = 0;
  120.             Uchar             *theBufferP    = inBufferP;
  121.             Boolean            keepGoing    = true;
  122.     
  123.     // validate inputs
  124.     ThrowIfNil_( inBufferP );
  125.     
  126.     currentState = state_begin;
  127.     while ( keepGoing && ( theBufferP < inBufferP + inSize ))    // for each character...
  128.     {
  129.         theChar    = *theBufferP++;
  130.         charLength    += 1;    // tracking for partial match at end of buffer
  131.         for ( ;; )    // determine the next state
  132.         {
  133.             if ( ( state_begin == currentState ) )         // branch shortcut
  134.             {
  135.                 charLength    = 1;    // assumes new strings always start at state_begin
  136.                 nextState = GotoArray[currentState].BranchTable[theChar];
  137.             }
  138.             else if ( BRANCH == ( matchChar = MatchArray[currentState] )) // branch
  139.             {
  140.                 nextState = GotoArray[currentState].BranchTable[theChar];
  141.             }
  142.             else if ( matchChar == theChar )            // single char
  143.             {
  144.                 nextState = GotoArray[currentState].GotoState;
  145.             }
  146.             else                                        // empty slot
  147.             {
  148.                 nextState = FAIL_STATE;
  149.             }
  150.             
  151.             if ( FAIL_STATE != nextState ) break;
  152.             
  153.             currentState = RetryArray[currentState];    // goto another word containing from char
  154.             Assert_( currentState < mMaxState );
  155.         }
  156.         currentState = nextState;
  157.         Assert_( currentState < mMaxState );
  158.         
  159.         // scan OutArray to see if a complete word is found
  160.         if ( OutArray[currentState].GetCount() > 0 )    // list not empty
  161.         {
  162.             // report for each string of the recognized string list
  163.             DSearchString    *reportItemP    = NULL;
  164.             LListIterator    foundList ( OutArray[currentState], iterate_FromStart );
  165.             while ( foundList.Next ( &reportItemP ) )    // for each output string...
  166.             {
  167.                 if ( NULL == reportItemP ) throw ( list_item_NULL() );
  168.                 keepGoing = reportItemP->ReportFound( (theBufferP-inBufferP) );
  169.             }
  170.         }
  171.     }
  172.     // Pass along the request to cancel the search.
  173.     if ( !keepGoing ) charLength = searchStatus_stop;    
  174.     
  175.     // Return indication of what part of the buffer should be reused.
  176.     return charLength;    
  177. }
  178.  
  179.  
  180. // ---------------------------------------------------------------------------
  181. //    Destructor
  182.  
  183. DMultiStringLocator::~DMultiStringLocator()
  184. {
  185.     stateT theState;
  186.     
  187.     // search and destroy BranchTables    
  188.     for ( theState = state_begin; theState < mMaxState; ++theState )
  189.     {
  190.         if ( BRANCH == MatchArray[theState] )
  191.         {
  192.             delete[] ( GotoArray[theState].BranchTable );
  193.         }
  194.     }
  195.     
  196.     // destroy other arrays
  197.     delete[] ( MatchArray    );
  198.     delete[] ( GotoArray    );
  199.     delete[] ( RetryArray    );
  200.     delete[] ( OutArray        );    // listed objects are just pointers
  201. }
  202.  
  203.  
  204. // ===========================================================================
  205. // ----------- private member function implementations ----------
  206. // ===========================================================================
  207. #pragma mark --- private members ---
  208. // ---------------------------------------------------------------------------
  209. //    Add inString to list of recognizable strings
  210.  
  211. void
  212. DMultiStringLocator::AddString( DSearchString &inString )
  213. {
  214.     stateT                currentState, nextState;
  215.     Uint8            charPos;
  216.     
  217.     currentState    = state_begin;
  218.     
  219.     if ( NULL == &inString ) throw ( input_item_NULL() );    // validate input
  220.     
  221.     // attempt to place new word on an old word
  222.     for ( charPos = 1; charPos <= inString.Length(); ++charPos )    // for each char...
  223.     {
  224.         if ( inString[charPos] == MatchArray[currentState] )        // single char slot
  225.         {
  226.             currentState = GotoArray[currentState].GotoState;
  227.             Assert_( currentState < mMaxState );
  228.         }
  229.         else if ( BRANCH == MatchArray[currentState] )        // branch
  230.         {
  231.             if ( FAIL_STATE == 
  232.                 ( nextState = GotoArray[currentState].BranchTable[inString[charPos]] ))
  233.             {
  234.                 break;    // char not part of a stored word
  235.             }
  236.             else        // a transition for this character
  237.             {
  238.                 currentState = nextState;
  239.             }
  240.             Assert_( currentState < mMaxState );
  241.         }
  242.         else                                                // empty slot
  243.         {
  244.             break;        // no match for this character
  245.         }
  246.     }
  247.     
  248.     // add new states as needed
  249.     for ( ; charPos <= inString.Length(); ++charPos )        // for each char...
  250.     {
  251.         mHighState += 1;
  252.         if ( mHighState >= mMaxState ) throw ( state_table_exceeded() );
  253.  
  254.         AddStateTrans ( inString[charPos], currentState, mHighState );
  255.         currentState = mHighState;
  256.     }
  257.     
  258.     // Place a copy of the pointer to the string in the output list
  259.     if ( 0 < inString.Length() )
  260.     {
  261.         OutArray[currentState].InsertItemsAt( 1, arrayIndex_Last, &(&inString) );
  262.     }
  263. }
  264.  
  265.  
  266. // ---------------------------------------------------------------------------
  267. //    Add transition for matchChar from currentState to nextState
  268.  
  269. void
  270. DMultiStringLocator::AddStateTrans ( int matchChar, stateT currentState, stateT nextState )
  271. {
  272.     int        theChar;
  273.     stateT    *newBranchTable;
  274.     
  275.     // validate inputs
  276.     Assert_( currentState < mMaxState );
  277.     Assert_( nextState < mMaxState );
  278.  
  279.     if ( EMPTY_SLOT == MatchArray[currentState] )        // empty slot
  280.     {
  281.         MatchArray[currentState] = matchChar;
  282.         GotoArray[currentState].GotoState = nextState;
  283.     }
  284.     else if ( BRANCH == MatchArray[currentState] )        // branch
  285.     {
  286.         GotoArray[currentState].BranchTable[matchChar] = nextState;
  287.     }
  288.     else                                                // single char
  289.     {
  290.         newBranchTable = new stateT[mAlphabetSize];
  291.         for ( theChar = 0; theChar < mAlphabetSize; ++theChar )
  292.         {
  293.             newBranchTable[theChar] = FAIL_STATE;
  294.         }
  295.         
  296.         // copy data from single-way branch to char position of newBranchTable
  297.         newBranchTable [ MatchArray [ currentState ] ] 
  298.             = GotoArray[ currentState ].GotoState;
  299.         
  300.         // copy new data
  301.         newBranchTable[matchChar] = nextState;
  302.         
  303.         // load state_array
  304.         MatchArray[currentState] = BRANCH;
  305.         GotoArray[currentState].BranchTable = newBranchTable;
  306.     }
  307. }
  308.  
  309.  
  310. // ---------------------------------------------------------------------------
  311. //    build RetryArray and update GotoArray
  312.  
  313. void
  314. DMultiStringLocator::RetryArrayInit( void )
  315. {
  316.     stateT    *fifo, fifoTop;
  317.     stateT    nextState, currentState;
  318.     int        theChar;
  319.     
  320.     // create fifo
  321.     fifo            = new stateT[mMaxState];    // a FIFO list of possible states
  322.     if ( NULL == fifo ) throw ( memerror() );
  323.     fifoTop            = 0;
  324.     fifo[fifoTop]    = 0;
  325.     
  326.     // initialize RetryArray with first level BranchTable states
  327.     for ( theChar = 0; theChar < mAlphabetSize; ++theChar )
  328.     {
  329.         if ( state_begin != 
  330.             ( currentState = GotoArray[state_begin].BranchTable[theChar] ))
  331.         {
  332.             Assert_( currentState < mMaxState );
  333.             RetryArray[currentState] = state_begin;
  334.             QueueAdd (fifo, fifoTop, currentState );
  335.         }
  336.     }
  337.     
  338.     // update RetryArray with states from scan of lower levels
  339.     while ( 0 != fifo[fifoTop] )    // for each state in the fifo...
  340.     {
  341.         // grab state from front of fifo and advance fifoTop to next item
  342.         nextState    = fifo[fifoTop];
  343.         fifoTop        = nextState;    // effectively drops retrieved item from the fifo
  344.         Assert_( nextState < mMaxState );
  345.         
  346.         // examine this state
  347.         if ( EMPTY_SLOT == MatchArray[nextState] )
  348.         {
  349.             continue;
  350.         }
  351.         else if ( BRANCH == MatchArray[nextState] )
  352.         {
  353.             // scan subsidiary states
  354.             for ( theChar = 0; theChar < mAlphabetSize; ++theChar )    // scan branch table
  355.             {
  356.                 if ( FAIL_STATE != 
  357.                     ( currentState = GotoArray[nextState].BranchTable[theChar] ))
  358.                 {
  359.                     Assert_( currentState < mMaxState );
  360.                     // add new state to the fifo
  361.                     QueueAdd ( fifo, fifoTop, currentState );
  362.                     FindRetryState ( theChar, RetryArray[nextState], currentState );
  363.                 }
  364.             }
  365.         }
  366.         else    // single character
  367.         {
  368.             QueueAdd ( fifo, fifoTop, GotoArray[nextState].GotoState );
  369.             FindRetryState ( MatchArray[nextState]
  370.                              , RetryArray[nextState], GotoArray[nextState].GotoState );
  371.         }
  372.     }
  373.     
  374.     delete[] ( fifo );
  375. }
  376.  
  377.  
  378. // ---------------------------------------------------------------------------
  379. //    add inItem to end of fifo (a FIFO list)
  380. //    fifo is actually a list implemented in an array. Each item points to the
  381. //    next intended item. Items are not necessarily added in order (maybe?).
  382.  
  383. void
  384. DMultiStringLocator::QueueAdd( int *fifo, int fifoTop, stateT inState )
  385. {
  386.     const    stateT    fifo_end = 0;
  387.             stateT    nextState;
  388.     
  389.     // validate inputs
  390.     Assert_( inState < mMaxState );
  391.     if ( NULL == fifo ) throw ( input_item_NULL() );    
  392.     
  393.     nextState = fifo[fifoTop];
  394.     if ( fifo_end == nextState )        // list is empty
  395.     {
  396.         fifo[fifoTop] = inState;
  397.     }
  398.     else
  399.     {
  400.         for ( ; fifo_end != fifo[nextState]; nextState = fifo[nextState] )
  401.         {
  402.             ;                                    // walk to the next open slot
  403.             Assert_( nextState < mMaxState );    // don't walk off the end of the list
  404.         }
  405.         fifo[nextState] = inState;                // insert state
  406.     }
  407.     fifo[inState] = fifo_end;    // terminate list
  408. }
  409.  
  410.  
  411. // ---------------------------------------------------------------------------
  412. //    Compute failure transition.
  413. //
  414. //    inChar would normally cause a change from currentState to nextState. To 
  415. //    compute the retry value, look back in search of other places inChar might go.
  416. //
  417. //    This appears to assume inChar is either a single char match or a branch. 
  418.  
  419. void
  420. DMultiStringLocator::FindRetryState
  421. (    const    int        inChar
  422. ,            stateT    currentState
  423. ,    const    stateT    nextState
  424. )
  425. {
  426.     stateT on_fail;
  427.     
  428.     // validate inputs
  429.     Assert_( currentState < mMaxState );
  430.     Assert_( nextState < mMaxState );
  431.  
  432.     // locate embedded words by following RetryArray until it does not link to FAIL_STATE
  433.     for ( ; ; currentState = RetryArray[currentState] )
  434.     {
  435.         Assert_( currentState < mMaxState );
  436.         if ( inChar == MatchArray[currentState] )            // desired single char
  437.         {
  438.             if ( FAIL_STATE != ( on_fail = GotoArray[currentState].GotoState )) break;
  439.         }
  440.         else if ( BRANCH == MatchArray[currentState] )        // branch
  441.         // 960630: Original code (below) fails when searching for { nil, int, Item }.
  442.         // else if ( EMPTY_SLOT != MatchArray[currentState] )
  443.         // Why was this not an explicit BRANCH test?  
  444.         {
  445.             if ( FAIL_STATE != 
  446.                 ( on_fail = GotoArray[currentState].BranchTable[inChar] ))    break;
  447.         }
  448.     }
  449.     
  450.     Assert_( on_fail < mMaxState );
  451.     RetryArray[nextState] = on_fail;
  452.     
  453.     // --- merge output lists
  454.     // duplicate each item of OutArray[on_fail] to end of list OutArray[nextState]
  455.     // ••• When can there every really be two words with the same end state?
  456.     if ( OutArray[on_fail].GetCount() > 0 )        // list not empty
  457.     {
  458.         void            *itemP    = NULL;
  459.         LListIterator    failList ( OutArray[on_fail], iterate_FromStart );
  460.         while ( failList.Next ( &itemP ) )        // for each on_fail output string...
  461.         {
  462.             OutArray[nextState].InsertItemsAt( 1, arrayIndex_Last, &itemP );
  463.         }
  464.     }
  465. }
  466.  
  467.  
  468. // ===========================================================================
  469.